<?php

declare (strict_types = 1);

namespace auth;

use auth\contract\Authenticatable;
use auth\contract\Guard;
use auth\contract\UserProvider;
use think\Request;

class TokenGuard implements Guard
{
    use GuardHelper;

    /**
     * 请求对象
     *
     * @var \think\Request
     */
    protected $request;

    /**
     * 传递 token 的参数名称
     *
     * @var string
     */
    protected $inputKey;

    /**
     * token 有效期
     *
     * @var int
     */
    protected $expiration;

    public function __construct(
        UserProvider $provider,
        Request $request,
        $inputKey = 'token',
        $expiration = null
    ) {
        $this->provider = $provider;
        $this->request = $request;
        $this->inputKey = $inputKey;
        $this->expiration = $expiration;
    }

    /**
     * 登入系统
     *
     * @param  array  $credentials
     * @return bool
     */
    public function attempt(array $credentials = []): bool
    {
        $this->user = $user = $this->provider->retrieveByCredentials($credentials);

        return $this->hasValidCredentials($user, $credentials);
    }

    /**
     * 获取当前认证用户
     *
     * @return \auth\contract\Authenticatable|null
     *
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function user(): ?Authenticatable
    {
        if (!is_null($this->user)) {
            return $this->user;
        }

        $token = $this->getTokenFromRequest();
        $instance = AccessToken::findToken($token);
        if (!$this->isValidAccessToken($instance)) {
            return null;
        }

        if (!hash_equals($instance->token, hash('sha256', $token))) {
            return null;
        }

        $user = $this->provider->retrieveByToken($instance);

        return $this->user = $user;
    }

    /**
     * 退出登录
     *
     * @return bool
     */
    public function logout(): bool
    {
        return AccessToken::destroyToken($this->getTokenFromRequest());
    }

    /**
     * 销毁认证用户的所有认证标识
     *
     * @return bool
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public function destroyAllTokens(): bool
    {
        return AccessToken::destroyTokensOfUser($this->user());
    }

    /**
     * 判断用户登录凭证是否合法
     *
     * @param  mixed  $user
     * @param  array  $credentials
     * @return bool
     */
    protected function hasValidCredentials($user, array $credentials): bool
    {
        return !is_null($user) && $this->provider->validateCredentials($user, $credentials);
    }

    /**
     * 从当前请求中获取 token
     *
     * @return string|null
     */
    protected function getTokenFromRequest(): ?string
    {
        $token = $this->request->param($this->inputKey);

        if (empty($token)) {
            $token = $this->request->bearerToken();
        }

        return $token;
    }

    /**
     * 判断 token 是否合法
     *
     * @param  mixed  $accessToken
     * @return bool
     */
    protected function isValidAccessToken($accessToken): bool
    {
        if (!$accessToken) {
            return false;
        }

        return !$this->expiration || (strtotime($accessToken->update_time) > (time() - $this->expiration * 60));
    }
}
